A debugger is a tool that aids software development by giving a user control over and access to a running program. Debuggers typically run as self-contained processes, controlling the program under study--the so-called application process--through operating system primitives designed for that purpose. A simple application program typically consists of data, and functions that operate on those data. Data and functions are defined in a source file created by the programmer. Each datum is associated with a type that describes its internal structure and behaviors; for example, integers may be 16 bits long, and may be added, subtracted, multiplied, etc. A tool called a compiler or translator reads a source file and produces an object file. The compiler typically works in conjunction with other tools-assemblers, linkers, and optimizers-to accomplish this task, and such ancillary tools are usually invisible to the programmer.
The object file contains bits which can be loaded into computer memory and executed; these bits include both the data and the machine instructions generated from the original program. These bits, when loaded into memory, are called the program image. In most systems, there is a close mapping of program image onto what the user perceives as a user process. The object file also contains a table that maps some of the original source information, as variable and function names, onto addresses, offsets, sizes, and other pertinent properties of the program image. This so-called symbol table is usually not made part of the program image itself, but remains in the object file where other programs (like the debugger) can read and analyze it.
A debugger is used to examine the program image of a program in execution, and to control the execution of the program. Because the debugger has access to the symbol table, it allows the user to interact with the target process in terms of the names found in the source code. For example, if the user knows the name of a variable, they can ask the debugger to retrieve that variable's current contents from the program image or from storage dynamically allocated at run time by giving the variable's name. By going to the symbol table and looking up the variable's address and type, the debugger obtains the information it needs to satisfy the user request. An analogous scenario might allow the user to modify the value of a variable's contents on the fly. These are usually done while the target program is stopped.
Debuggers are most often used to intercept or monitor the thread of control through a running program. It is usually the case that one or the other of the debugger or the target program are active, but not both. If the target is running, the user can interact directly with it while the debugger lies dormant. If the debugger is running, the user has its attention and the application target is usually stopped; i.e., its program counter advances no further. When the debugger is running, it is said to be in control; when the debugger causes the target to begin (or resume) execution, it relinquishes control.
The debugger can arrange to regain control when the target program counter reaches some pre-specified address. The debugger can deposit a machine instruction at that address, designed to cause some trap or to cause an operating system service to be called when it is executed. By virtue of prior arrangements between the debugger and the operating system, two things happen when the target reaches one of these instructions: 1) execution of the target process is put aside (i.e., the target process is stopped); and 2) the debugger is notified of the event and gains control. The debugger is able to determine the location of the event by examining program image state information saved by the operating system. Such special instructions, or the locii of such instructions, are called breakpoints. The event of execution arriving at a breakpoint is called the firing of a breakpoint.
Breakpoints are usually set at the direction of a user, who may want to know if and when execution reaches a certain point in a program, and may further wish to examine state information after the breakpoint fires. The user specifies where to insert the breakpoint: it may be on entry to a function, or at a location corresponding to some line number in the source code. The symbol table can be used to map function names and line numbers onto program image addresses. Some debuggers allow additional types of breakpoints, e.g., on return from a function.
There is a direct mapping between the structure of the source, and the structure of the program image, when a simple procedural language such as C, FORTRAN, or Pascal is used. If there is a function f in the source, then there is code for f in the program image. Furthermore, both of these structurings map directly onto the abstractions a programmer deals with in creating and administering a program. For example, if a function appears in the source, it will appear in the program image as a likewise contiguous entity.
However, with object-oriented languages such as C++, it is useful to think of objects as associating themselves with different functions over the execution of the program. For example, a window variable may be used for a window based on one set of functions at the beginning of the program, and a different set of functions later on. These functions may correspond to, for example, different hardware windowing technologies that are connected to the system where the program is running. The major strength of object-oriented programming is in the way it combines inheritance with run-time operator identification using virtual functions. The specific identity of a virtual function when invoked by name is to be determined at run time as a function of the object with which it is associated. In debugging object-oriented programs, it would be desirable to allow a user to insert a breakpoint that will fire whenever a specified virtual function is invoked on a specified object. However, since the symbol table is generated as part of program compilation, it cannot include sufficient information to determine where to insert the breakpoint. Thus, known debuggers that rely on only symbol table information to determine breakpoint addresses are not able to insert breakpoints that fire under conditions involving a run time association of functions with objects.